package evobot {
	import com.adobe.crypto.SHA1;
	
	import flash.errors.*;
	import flash.events.*;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	
	import mx.utils.ObjectUtil;
	
	internal class Connection extends Socket {
		private var client:GameClient = GameClient.getInstance();
		private var user:String;
		private var password:String;
		
		private var lengthRead:Boolean = false;
		private var length:int = 0;
			
		public function Connection(host:String, port:uint, user:String, password:String) {
			super();
	       	configureListeners();
        	if (host && port && user && password)  {
        		trace("connect: " + host + " " + port);
	       	    try {
	       	    	super.connect(host, port);
	       	    }
	       	    catch(e:IOError) {
	       	    	// This is supposed to happen when no host is specified.
	       	    	var obj:Object = new Object();
	       	    	obj.ok = -100;
	       	    	obj.errorMessage = "Invalid Server";
	       	    	client.loginResponse(obj); 	   
	       	    	close();    	    	
	       	    }
	       	    catch(e:SecurityError) {
	       	    	// This is supposed to happen if the local player isn't allowed access to the Internet.
	       	    	var obj2:Object = new Object();
	       	    	obj2.ok = -101;
	       	    	obj2.errorMessage = "You brower's security does not allow this program to reach the Internet.";
	       	    	client.loginResponse(obj2);
	       	    	close(); 
	       	    }
    			this.client = GameClient.getInstance();
    			this.user = user;
    			this.password = password;   	    
       		} else {
       			var obj3:Object = new Object();
	       	    	obj3.ok = -103;
	       	    	obj3.errorMessage = "Invalid Parameters";
	       	    	client.loginResponse(obj3);
	       	    	close(); 
       		}
		}
	
		public function sendRequest(cmd:String = null, data:Object = null):void {
			var request:Object = new Object();
			request.data = data;
			request.cmd = cmd;
			var bytes:ByteArray = new ByteArray();
			bytes.writeObject(request);
			try {
				writeInt(bytes.length);
				writeBytes(bytes);
				flush();
			} 
			catch(e:IOError) {
				trace(e);
				close();
				client.lostConnection();
			}		
		}

		override public function close():void {
			removeEventListener(Event.CLOSE, closeHandler);
        	removeEventListener(Event.CONNECT, connectHandler);
        	removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
        	removeEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
        	removeEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
			try {
				super.close();
			} catch (e:IOError) { }			 	
		}
		
		private function configureListeners():void {
	        addEventListener(Event.CLOSE, closeHandler);
        	addEventListener(Event.CONNECT, connectHandler);
        	addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
        	addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
        	addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
    	}
    	
    	private function closeHandler(event:Event):void {
    		// Server closed the connection.
	        trace("closeHandler: " + event);
	        close();
	        client.lostConnection();
    	}

    	private function connectHandler(event:Event):void {
    		// Network connection is established.
	        trace("connectHandler: " + event);
	        Log.getInstance().write("Connected.");
		    sendRequest("gameClient.version", client.VERSION);

			var loginInfo:Object = new Object();
			loginInfo["user"] = this.user;
			loginInfo["pwd"] = SHA1.hash(this.password);
			sendRequest("login", loginInfo);
			trace("sent login information");
    	}

    	private function ioErrorHandler(event:IOErrorEvent):void {
    		// Connection lost. event.text contains an error message.
	        trace("ioErrorHandler: " + event);
	        close();
	        client.lostConnection();
	    }

	    private function securityErrorHandler(event:SecurityErrorEvent):void {
	    	// We will treat this as a network timeout, it couldn't load the cross domain policy file.
        	trace("securityErrorHandler: " + event);
        	var obj:Object = new Object();
	       	obj.ok = -102;
	       	obj.errorMessage = "Network Timeout.";
	       	client.loginResponse(obj);
	       	close(); 
    	}

    	private function socketDataHandler(event:ProgressEvent):void {
    		trace("socketDataHandler: " + event);
    		while(true) {
    			if(!lengthRead) {
    				if (bytesAvailable >= 4) {
    					length = readInt();
    					if (length == 0) {
    						continue;
    					} 
    					if (length < 0 || length > 1000000) {
    						close();
    						client.lostConnection();
    						throw new Error("Invalid AMF stream.");
    						return;    						
    					}
    					lengthRead = true;
    				} else return;
    			}
    			if (bytesAvailable >= length) {
    				try {
						var incomming:Object = readObject();
    				} catch (e:Error) {
						close();
    					client.lostConnection();
    					throw new Error("Invalid AMF stream.");
    					return;
					}
					if (incomming.hasOwnProperty("cmd") && incomming.hasOwnProperty("data")) {
						trace("received object");
						processData(incomming.cmd, incomming.data);
					} else {
						close();
    					client.lostConnection();
    					throw new Error("Invalid AMF stream.");
    					return;
					}	    								
					length = 0;
					lengthRead = false;
    			} else return;	   		        	
	   		}    	
		}
		
		private function processData(cmd:String, obj:Object):void {
			switch (cmd) {
				case "server.LoginResponse":
					client.loginResponse(obj);
					return;
				case "server.SystemInfoMsg":
					return;
				case "server.ChannelChatMsg":
					client.channelChatMsg(obj);
					return;
				case "server.ConnectionLost":
					if (obj.reasonCode == 3) {
						client.otherLogin();
					} // don't return and keep this at the bottom, might be other reason codes
				default:
					trace("unhandled command: " + cmd);
					trace(ObjectUtil.toString(obj));
					return;			
			}
				
		}
	}
}